<?php
namespace WDB\Wrapper;
use WDB,
    WDB\Exception,
    WDB\GTO\WsqlLanguage,
    WDB\Query\Element;

/**
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 */
class TimeDependentTable extends Table
{
    const FROM_COLUMN = '~from';
    const TO_COLUMN = '~to';
    const ID_COLUMN = 'id~td';
    const GLOBAL_TABLE_ALIAS = '~tdGlobal';
    const FROM_COLUMN_GLOBAL_ALIAS = '~fromGlobal';
    const TO_COLUMN_GLOBAL_ALIAS = '~toGlobal';

    protected $timeTableAnalyzer;

    /**@var int timestamped current date*/
    protected $currentDate;

    /**@var array*/
    protected $masterColumns;
    protected $timeDependentColumns;

    public function __construct(WDB\Analyzer\iTable $tableAnalyzer)
    {
        parent::__construct($tableAnalyzer);
        $this->masterColumns = $this->columns;
        $this->currentDate = time();
        $this->timeTableAnalyzer = $tableAnalyzer->getTimeDependencyTable();
        $this->initBasicColumns($this->timeTableAnalyzer->getColumns());
        if (!isset($this->columns[self::FROM_COLUMN]) || !isset($this->columns[self::TO_COLUMN]))
        {
            throw new Exception\InvalidModel("Time dependency table {$this->name} must have ".self::FROM_COLUMN." and ".self::TO_COLUMN." date type columns.");
        }

        $this->columns[self::FROM_COLUMN]->removeDisplayModes |= WDB\WebUI\iColumn::DISPLAY_ALL;
        $this->columns[self::TO_COLUMN]->removeDisplayModes |= WDB\WebUI\iColumn::DISPLAY_ALL;

        $fk = $this->timeTableAnalyzer->getForeignKeys();
        foreach ($fk as $name=>$key)
        {
            if ($key->refSchema == $this->schema->getName() && $key->refTable == $this->name)
            {
                unset($fk[$name]);
                break;
            }
        }
        $this->initForeignColumns($fk);
        $this->timeDependentColumns = WDB\Utils\Arrays::diff($this->columns, $this->masterColumns);
    }

    public function getMasterColumns()
    {
        return $this->masterColumns;
    }

    public function getTimeDependentColumns()
    {
        return $this->timeDependentColumns;
    }

    /**
     *
     * @return WDB\Analyzer\iTable
     */
    public function getTimeTableAnalyzer()
    {
        return $this->timeTableAnalyzer;
    }

    /**
     *
     * @return WDB\iDatasource
     */
    public function getSourceTableQuery()
    {
        $stq = $this->getAllTimesSourceTableQuery();
        if ($this->currentDate !== NULL)
        {
            $date = date('Y-m-d', $this->currentDate);
            $fromColumn = WsqlLanguage::quoteIdentifier(self::FROM_COLUMN);
            $toColumn = WsqlLanguage::quoteIdentifier(self::TO_COLUMN);
            $tdName = WsqlLanguage::quoteIdentifier($this->getTimeTableAnalyzer()->getName());
            $tdSchema = WsqlLanguage::quoteIdentifier($this->getTimeTableAnalyzer()->getSchema()->getName());
            if ($tdSchema) $tdSchema .= '.';
            $stq->addCondition(WsqlLanguage::parse(
                sprintf("($tdSchema$tdName.$fromColumn <= '%s' OR $tdSchema$tdName.$fromColumn = NULL) AND ($tdSchema$tdName.$toColumn > '%s' OR $tdSchema$tdName.$toColumn = NULL)", $date, $date),
                'Condition'));
        }
        return $stq;
    }

    protected function getAllTimesSourceTableQuery()
    {

        $stq = parent::getSourceTableQuery()
                ->join($this->timeTableAnalyzer->getIdentifier())
                ->join($this->timeTableAnalyzer->getIdentifier()->alias(self::GLOBAL_TABLE_ALIAS), Element\JoinUsing::create($this->getMasterPrimaryKey()));
        foreach ($this->getMasterPrimaryKey() as $column)
        {
            $stq->addGroup(Element\ColumnIdentifier::create($column, $this->tableAnalyzer->getName(), $this->tableAnalyzer->getSchema()->getName()));
        }
        return $stq;
    }



    /**
     * @see getDatasource()
     * @param int\DateTime $time timestamp
     * @param array $cfg
     * @return WDB\iDatasource
     */
    public function getDatasourceForTime($time, array $cfg = array())
    {
        return $this->timeSpanOperation('getDatasource', $time, array($cfg));
    }

    /**
     * @see getDatasource()
     * @param array $cfg
     * @return WDB\iDatasource
     */
    public function getAllTimesDatasource(array $cfg = array())
    {
        return $this->timeSpanOperation('getDatasource', NULL, array($cfg));
    }

    /**
     *
     * @see getRecordByKey()
     * @param int|\DateTime $time timestamp
     * @param array|mixed $key
     * @return iRecord
     */
    public function getRecordByKeyForTime($time, $key)
    {
        return $this->timeSpanOperation('getRecordByKey', $time, array($key));
    }

    /**
     *
     * @see getRecordByKey()
     * @param int|\DateTime $time timestamp
     * @param string $key
     * @return iRecord
     */
    public function getRecordByStringPKForTime($time, $key)
    {
        return $this->timeSpanOperation('getRecordStringPK', $time, array($key));
    }

    protected function timeSpanOperation($method, $time, $args)
    {
        if ($time instanceof \DateTime)
        {
            $time = $time->getTimestamp();
        }
        $t = $this->currentDate;
        $this->currentDate = $time;
        $result = call_user_func_array(array('parent', $method), $args);
        $this->currentDate = $t;
        return $result;
    }

    /**
     *
     * @param array|mixed $key
     * @return Structure\TimeSpan[]
     */
    public function getRecordTimeSpanList($key)
    {
        $result = array();
        $key = $this->canonicalizeKey($key);
        foreach ($this->getAllTimesDatasource()->select(self::FROM_COLUMN, self::TO_COLUMN, self::ID_COLUMN)->filter($key)->run($this->database) as $row)
        {
            $result[$row[self::ID_COLUMN]] = Structure\TimeSpan::create($this, $row[self::ID_COLUMN], $row[self::FROM_COLUMN], $row[self::TO_COLUMN]);
        }
        return $result;
    }

    public function getRecordByTimeId($id)
    {
        $result = $this->getAllTimesDatasource()->filter(array(self::ID_COLUMN=>$id))->run($this->database);
        if (count($result) == 0) throw new Exception\NotFound ("record not found.");
        return $this->fetchRecord($result->singleRow());
    }

    public function getRecordWrapperClass()
    {
        return WDB\Utils\System::coalesce(parent::getRecordWrapperClass(), '\\WDB\\Wrapper\\TimeDependentRecord');
    }

    public function fetchValidators(WDB\Event\Event $event)
    {
        parent::fetchValidators($event);
        $this->timeTableAnalyzer->fetchValidators($event);
    }

    public function getPrimaryKey()
    {
        return array(self::ID_COLUMN);
    }

    public function getMasterPrimaryKey()
    {
        return parent::getPrimaryKey();
    }

    public function initReadFields()
    {
        parent::initReadFields();
        $fromColumn = WsqlLanguage::quoteIdentifier(self::GLOBAL_TABLE_ALIAS).'.'.WsqlLanguage::quoteIdentifier(self::FROM_COLUMN);
        $toColumn = WsqlLanguage::quoteIdentifier(self::GLOBAL_TABLE_ALIAS).'.'.WsqlLanguage::quoteIdentifier(self::TO_COLUMN);
        $this->readFields[] = new WDB\Query\Element\SelectField(WsqlLanguage::parse("IF(SUM($fromColumn = NULL), NULL, MIN($fromColumn))", 'Expression'), self::FROM_COLUMN_GLOBAL_ALIAS);
        $this->readFields[] = new WDB\Query\Element\SelectField(WsqlLanguage::parse("IF(SUM($toColumn = NULL), NULL, MAX($toColumn))", 'Expression'), self::TO_COLUMN_GLOBAL_ALIAS);
    }

}